<?php
namespace WeChatPay;

use Psr\Http\Message\ResponseInterface;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Request\AbstractBaseRequest;
use WeChatPay\Request\WeChatPayTradeClientOpenWxSignRequest;
use WeChatPay\Request\WeChatPayTradeNotifyVerifyRequest;
use WeChatPay\Request\WeChatPayTradeServiceSignRequest;

/**
 * 微信支付核心处理类
 * Class WeChatTradeService
 * @package WeChatPay
 */
class WeChatTradeService
{
    /**
     * 商户号
     * @var string
     */
    private $mchid = "";

    /**
     * 商户证书序列号
     * @var string
     */
    private $serial = "";

    /**
     * 商户私钥 资源文件\WeChatPay\Util\PemUtil::loadPrivateKey('/apiclient_key.pem')
     * @var string
     */
    private $privateKey = "";

    /**
     * [$serial => $certificate] 注意这里是平台证书，可以有多个, $certificate是资源文件\WeChatPay\Util\PemUtil::loadPrivateKey('/wechatpay_pt.pem')
     * @var array
     */
    private $certs = [];

    /**
     * 微信分配的公众账号ID（企业号corpid即为此appId）必填
     * @var string
     */
    private $appid = "";

    /**
     * 异步回调地址
     * @var string
     */
    private $notify_url = "";

    /**
     * 退款通知地址
     * @var string
     */
    private $refund_notify_url = "";

    /**
     * 网站地址
     * @var string
     */
    private $wap_url = "";

    /**
     * 网站名称
     * @var string
     */
    private $wap_name = "";

    /**
     * 请求体对象
     * @var AbstractBaseRequest
     */
    private $requestObj = null;

    /**
     * 请求HTTP对象
     * @var null
     */
    private $requestHttpObj = null;

    /**
     * 响应体对象
     * @var ResponseInterface
     */
    private $responseObj = null;

    public function __construct($config)
    {
        $this->setConfig($config);
    }

    /**
     * 设置配置文件
     * @param $config
     */
    public function setConfig($config) {
        if (!(isset($config['mchid']) && is_string($config['mchid']))) {
            throw new Exception\InvalidArgumentException(Exception\ERR_INIT_MCHID_IS_MANDATORY);
        }

        if (!(isset($config['serial']) && is_string($config['serial']))) {
            throw new Exception\InvalidArgumentException(Exception\ERR_INIT_SERIAL_IS_MANDATORY);
        }

        if (!(isset($config['privateKey']) && (is_string($config['privateKey']) || is_resource($config['privateKey']) || is_object($config['privateKey'])))) {
            throw new Exception\InvalidArgumentException(Exception\ERR_INIT_PRIVATEKEY_IS_MANDATORY);
        }

        if (!(isset($config['certs']) && is_array($config['certs']) && count($config['certs']))) {
            throw new Exception\InvalidArgumentException(Exception\ERR_INIT_CERTS_IS_MANDATORY);
        }

        if (array_key_exists($config['serial'], $config['certs'])) {
            throw new Exception\InvalidArgumentException(sprintf(
                Exception\ERR_INIT_CERTS_EXCLUDE_MCHSERIAL, implode(',', array_keys($config['certs'])), $config['serial']
            ));
        }
        $this->mchid = $config['mchid'];
        $this->serial = $config['serial'];
        $this->privateKey = $config['privateKey'];
        $this->certs = $config['certs'];
        $this->appid = array_key_exists('appid', $config) ? $config['appid'] : "";
        $this->notify_url = array_key_exists('notify_url', $config) ? $config['notify_url'] : "";
        $this->refund_notify_url = array_key_exists('refund_notify_url', $config) ? $config['refund_notify_url'] : "";
        $this->wap_url = array_key_exists('wap_url', $config) ? $config['wap_url'] : "";
        $this->wap_name = array_key_exists('wap_name', $config) ? $config['wap_name'] : "";
        return $this;
    }

    /**
     * 重置当前类
     */
    public function reset() {
        $this->requestHttpObj = null;
        $this->requestObj = null;
        $this->responseObj = null;
        $this->mchid = '';
        $this->serial = '';
        $this->privateKey = '';
        $this->certs = [];
        $this->appid = '';
        $this->notify_url = '';
        $this->refund_notify_url = '';
        $this->wap_url = '';
        $this->wap_name = '';
        return $this;
    }

    /**
     * @return string
     */
    public function getMchid(): string
    {
        return $this->mchid;
    }

    /**
     * @param string $mchid
     */
    public function setMchid(string $mchid): void
    {
        $this->mchid = $mchid;
    }

    /**
     * @return string
     */
    public function getSerial(): string
    {
        return $this->serial;
    }

    /**
     * @param string $serial
     */
    public function setSerial(string $serial): void
    {
        $this->serial = $serial;
    }

    /**
     * @return string
     */
    public function getPrivateKey()
    {
        return $this->privateKey;
    }

    /**
     * @param string $privateKey
     */
    public function setPrivateKey(string $privateKey): void
    {
        $this->privateKey = $privateKey;
    }

    /**
     * @return array
     */
    public function getCerts(): array
    {
        return $this->certs;
    }

    /**
     * @param array $certs
     */
    public function setCerts(array $certs): void
    {
        $this->certs = $certs;
    }

    /**
     * @return string
     */
    public function getAppId(): string
    {
        return $this->appid;
    }

    /**
     * @param string $app_id
     */
    public function setAppId(string $appid): void
    {
        $this->appid = $appid;
    }

    /**
     * @return string
     */
    public function getNotifyUrl(): string
    {
        return $this->notify_url;
    }

    /**
     * @param string $notify_url
     */
    public function setNotifyUrl(string $notify_url): void
    {
        $this->notify_url = $notify_url;
    }

    /**
     * @return string
     */
    public function getRefundNotifyUrl(): string
    {
        return $this->refund_notify_url;
    }

    /**
     * @param string $refund_notify_url
     */
    public function setRefundNotifyUrl(string $refund_notify_url): void
    {
        $this->refund_notify_url = $refund_notify_url;
    }

    /**
     * @return string
     */
    public function getWapUrl(): string
    {
        return $this->wap_url;
    }

    /**
     * @param string $wap_url
     */
    public function setWapUrl(string $wap_url): void
    {
        $this->wap_url = $wap_url;
    }

    /**
     * @return string
     */
    public function getWapName(): string
    {
        return $this->wap_name;
    }

    /**
     * @param string $wap_name
     */
    public function setWapName(string $wap_name): void
    {
        $this->wap_name = $wap_name;
    }

    /**
     * 设置请求体对象
     * @param AbstractBaseRequest $request
     */
    public function setRequestObj(AbstractBaseRequest $request) {
        $this->requestObj = $request;
    }

    /**
     * 获取请求体对象
     * @return AbstractBaseRequest
     */
    public function getRequestObj() {
        return $this->requestObj;
    }

    /**
     * 获取请求参数列表
     * @return array
     * @throws \Exception
     */
    protected function getParams() {
        $requestParamsInfo = $this->requestObj->getRequestParams();
        if ($requestParamsInfo['code'] != WX_RETURN_SUCCESS) {
            throw new \Exception($requestParamsInfo['msg'], $requestParamsInfo['code']);
        }

        $requestParams = array_merge($requestParamsInfo['data'], $this->getBaseParams($this->requestObj->getBaseParams()));
        return $requestParams;
    }

    /**
     * 获取Http请求对象
     * @return BuilderChainable|null
     */
    public function getHttpRequestInstance() {
        if ($this->requestHttpObj) {
            return $this->requestHttpObj;
        }

        $config = [
            'mchid'         =>  $this->getMchid(),
            'serial'        =>  $this->getSerial(),
            'privateKey'    =>  $this->getPrivateKey(),
            'certs'         =>  $this->getCerts()
        ];
        $this->requestHttpObj = Builder::factory($config);
        return $this->requestHttpObj;
    }

    /**
     * 发起Api GET请求
     * @param BuilderChainable $requestHttpObj
     * @param $apiUri
     * @param $params
     * @return ResponseInterface
     */
    protected function requestGetApi($requestHttpObj, $apiUri, $params) {
        if ($params) {
            $this->responseObj = $requestHttpObj->chain($apiUri)->get(['query' => $params]);
        } else {
            $this->responseObj = $requestHttpObj->chain($apiUri)->get();
        }
        return $this->responseObj;
    }

    /**
     * 发起Api POST请求
     * @param BuilderChainable $requestHttpObj
     * @param $apiUri
     * @param $params
     * @return ResponseInterface
     */
    protected function requestPostApi($requestHttpObj, $apiUri, $params) {
        if ($params) {
            $this->responseObj = $requestHttpObj->chain($apiUri)->post(['json' => $params]);
        } else {
            $this->responseObj = $requestHttpObj->chain($apiUri)->post();
        }
        return $this->responseObj;
    }

    /**
     * 获取基础参数(配置文件中变量)
     * @param array $paramList
     * @return array
     */
    protected function getBaseParams($paramList = []) {
        $relParamsList = [];
        foreach ($paramList as $paramName) {
            if ($paramName == "refund_notify_url") {
                $relParamsList["notify_url"] = $this->{$paramName};
                continue;
            }
            $relParamsList[$paramName] = $this->{$paramName};
        }
        return $relParamsList;
    }

    /**
     * 根据Request对象进行接口请求并返回响应的值
     * @param AbstractBaseRequest|null $request
     * @return array
     */
    public function getResponse(AbstractBaseRequest $request = null) {
        if ($request) {
            $this->setRequestObj($request);
        }

        try {
            if ($this->getRequestObj()->getRequestType() == 'post') {
                $response = $this->requestPostApi($this->getHttpRequestInstance(), $this->getRequestObj()->getApiUri(), $this->getParams());
            } else {
                $response = $this->requestGetApi($this->getHttpRequestInstance(), $this->getRequestObj()->getApiUri(), $this->getParams());
            }

            $code = $response->getStatusCode();
            return FuncReturn($code, GetHttpCodeInfo($code), $response->getBody()->__toString());
        } catch (\Throwable $exception) {
            return FuncReturn(WX_RETURN_FAIL, $exception->getMessage());
        }
    }

    /**
     * 创建服务端签名
     * @return array
     */
    public function serviceSign(WeChatPayTradeServiceSignRequest $request) {
        $nonce = $request->getNonce();
        $timestamp = $request->getTimestamp();
        $requestType = $request->getRequestType();
        $urlInfo = parse_url($request->getApiUri());
        $requestTarget = trim($urlInfo['path'] ?? "/", "/");
        $body = $request->getBody();
        $signature = Crypto\Rsa::sign(Formatter::request($requestType, $requestTarget, $timestamp, $requestTarget, $body), $this->privateKey);
        if ($signature) {
            return FuncReturn(WX_RETURN_SUCCESS, '签名成功', [
                'nonce' =>  $nonce,
                'timestamp' =>  $timestamp,
                'requestType'   =>  $requestType,
                'requestTarget' =>  $requestTarget,
                'body'          =>  $body,
                'sign'          =>  $signature
            ]);
        } else {
            return FuncReturn(WX_RETURN_FAIL, '生成签名失败');
        }
    }

    /**
     * 创建客户端调起微信支付签名
     * @param WeChatPayTradeClientOpenWxSignRequest $request
     * @return array
     */
    public function clientOpenWxSign(WeChatPayTradeClientOpenWxSignRequest $request) {
        $nonce = $request->getNonce();
        $timestamp = $request->getTimestamp();
        $body = $request->getBody();
        $appId = $this->getAppId();
        $signature = Rsa::sign(Formatter::joinedByLineFeed($appId, $timestamp, $nonce, $body), $this->privateKey);
        if ($signature) {
            return FuncReturn(WX_RETURN_SUCCESS, '签名成功', [
                'nonce' =>  $nonce,
                'timestamp' =>  $timestamp,
                'appId'     =>  $appId,
                'body'          =>  $body,
                'sign'          =>  $signature
            ]);
        } else {
            return FuncReturn(WX_RETURN_FAIL, '生成签名失败');
        }
    }

    /**
     * 异步回调验签
     * @param WeChatPayTradeNotifyVerifyRequest $request
     * @return array
     * @throws \Exception
     */
    public function notifyVerify(WeChatPayTradeNotifyVerifyRequest $request) {
        if (!array_key_exists($request->getWeChatPaySerial(), $this->getCerts())) {
            throw new \Exception('平台证书文件不存在');
        }

        $verifiedStatus = Rsa::verify(
            Formatter::joinedByLineFeed($request->getWeChatPayTimestamp(), $request->getWeChatPayNonce(), $request->getWeChatPayBody()),
            $request->getWeChatPaySignature(),
            $this->getCerts()[$request->getWeChatPaySerial()]
        );
        return FuncReturn($verifiedStatus ? WX_RETURN_SUCCESS : WX_RETURN_FAIL, '验签完成');
    }
}